package msu.ml.util;

import msu.ml.data.level2.*;
import weka.core.*;
import java.io.*;

public class ScanDataConverter
{
    public ScanDataConverter()
    {

    }

    private int convolutionWidth;
    public int getConvolutionWidth() { return convolutionWidth; }
    public void setConvolutionWidth(int value) { convolutionWidth = value; }

    public Instances convertToInstances(String name, Scan ref, Scan dop)
    {
        return convertToInstances(name, ref, dop, false, false, false);
    }

    public Instances convertToInstancesComplete(String name, Scan ref, Scan dop)
    {
        int featureCount = 17;
        FastVector v = new FastVector(featureCount);
        FastVector classes = new FastVector(2);
        classes.addElement("0"); classes.addElement("1");

        v.addElement(new Attribute("x"));
        v.addElement(new Attribute("y"));
        v.addElement(new Attribute("azimuth"));
        v.addElement(new Attribute("range"));
        v.addElement(new Attribute("reflectivity"));
        v.addElement(new Attribute("radial_velocity"));
        v.addElement(new Attribute("spectrum_width"));
        v.addElement(new Attribute("ref_variance"));
        v.addElement(new Attribute("ref_kurtosis"));
        v.addElement(new Attribute("ref_skewness"));
        v.addElement(new Attribute("vel_variance"));
        v.addElement(new Attribute("vel_kurtosis"));
        v.addElement(new Attribute("vel_skewness"));
        v.addElement(new Attribute("spec_variance"));
        v.addElement(new Attribute("spec_kurtosis"));
        v.addElement(new Attribute("spec_skewness"));
        v.addElement(new Attribute("class", classes));

        Instances result = new Instances(name, v, 20000);

        double [][][] rdata = new double[ref.numRays()][][];
        double [][][] vdata = new double[dop.numRays()][][];
        double [][][] sdata = new double[dop.numRays()][][];

        for(int j = 0; j < ref.numRays(); j++)
        {
            double [] data = ref.getRay(j).getReflectivityData();

            rdata[j] = new double[data.length][4];
            for(int k = 0; k < data.length; k++)
                rdata[j][k][0] = data[k];
        }


        for(int j = 0; j < dop.numRays(); j++)
        {
            double [] data = dop.getRay(j).getVelocityData();
            vdata[j] = new double[data.length][4];
            for(int k = 0; k < data.length; k++)
                vdata[j][k][0] = data[k];

            data = dop.getRay(j).getSpectrumWidthData();
            sdata[j] = new double[data.length][4];
            for(int k = 0; k < data.length; k++)
                sdata[j][k][0] = data[k];
        }


        System.out.println("Calculating Reflectivity Derived Features");
        DataUtility.calcFeatures(rdata, convolutionWidth);
        System.out.println("Calculating Radial Velocity Derived Features");
        DataUtility.calcFeatures(vdata, convolutionWidth);
        System.out.println("Calculating Spectrum Width Derived Features");
        DataUtility.calcFeatures(sdata, convolutionWidth);

        boolean mixedGateSize = (ref.getRay(0).getReflectivityGateSize() != dop.getRay(0).getVelocityGateSize());

        for(int j = 0; j < ref.numRays(); j++)
        {
            double azimuth = ref.getRay(j).getAzimuth();
            int [] neighbors = findNeighbors(dop, azimuth);

            for(int i = 0; i < rdata[j].length; i++)
            {
                if(rdata[j][i][0] == (double)Ray.EMPTY)
                    continue;

                if((mixedGateSize ? (i * 4 + 4) : i) < vdata[neighbors[0]].length)
                {
                    double [] features = new double[featureCount];
                    int featureIndex = 0;

                    double r = i;
                    double theta = azimuth - 90;
                    if(theta < 0)
                        theta += 360;
                    if(theta != 0)
                        theta = 360 - theta;

                    features[featureIndex++] = r * Math.cos(theta * Math.PI / 180.0);
                    features[featureIndex++] = r * Math.sin(theta * Math.PI / 180.0);
                    features[featureIndex++] = azimuth;
                    features[featureIndex++] = i;

                    features[featureIndex++] = rdata[j][i][0];
                    features[featureIndex++] = averageNeighbors(vdata, 
                            neighbors[0], neighbors[1], i, 0, mixedGateSize);
                    features[featureIndex++] = averageNeighbors(sdata, 
                            neighbors[0], neighbors[1], i, 0, mixedGateSize);

                    features[featureIndex++] = rdata[j][i][1];
                    features[featureIndex++] = rdata[j][i][3];
                    features[featureIndex++] = rdata[j][i][2];
                    features[featureIndex++] = averageNeighbors(vdata, 
                            neighbors[0], neighbors[1], i, 1, mixedGateSize);
                    features[featureIndex++] = averageNeighbors(vdata, 
                            neighbors[0], neighbors[1], i, 3, mixedGateSize);
                    features[featureIndex++] = averageNeighbors(vdata, 
                            neighbors[0], neighbors[1], i, 2, mixedGateSize);
                    features[featureIndex++] = averageNeighbors(sdata, 
                            neighbors[0], neighbors[1], i, 1, mixedGateSize);
                    features[featureIndex++] = averageNeighbors(sdata, 
                            neighbors[0], neighbors[1], i, 3, mixedGateSize);
                    features[featureIndex++] = averageNeighbors(sdata, 
                            neighbors[0], neighbors[1], i, 2, mixedGateSize);

                    features[featureIndex++] = 0;

                    result.add(new Instance(1.0, features));
                }
            }
        }

        result.setClassIndex(result.numAttributes() - 1);

        return result;
    }

    public Instances convertToInstances(String name, Scan ref, Scan dop, 
            boolean polar, boolean forClass, boolean extended)
    {
        int featureCount = 5 + (forClass ? 1 : 0) + (extended ? 9 : 0);
        FastVector v = new FastVector(featureCount);
        if(polar)
        {
            v.addElement(new Attribute("x"));
            v.addElement(new Attribute("y"));
        }
        else
        {
            v.addElement(new Attribute("azimuth"));
            v.addElement(new Attribute("range"));
        }

        v.addElement(new Attribute("reflectivity"));
        v.addElement(new Attribute("radial_velocity"));
        v.addElement(new Attribute("spectrum_width"));

        if(extended)
        {
            v.addElement(new Attribute("ref_variance"));
            v.addElement(new Attribute("ref_kurtosis"));
            v.addElement(new Attribute("ref_skewness"));
            v.addElement(new Attribute("vel_variance"));
            v.addElement(new Attribute("vel_kurtosis"));
            v.addElement(new Attribute("vel_skewness"));
            v.addElement(new Attribute("spec_variance"));
            v.addElement(new Attribute("spec_kurtosis"));
            v.addElement(new Attribute("spec_skewness"));
        }

        if(forClass)
        {
            FastVector classes = new FastVector(2);
            classes.addElement("0");
            classes.addElement("1");
            v.addElement(new Attribute("class", classes));
        }

        Instances result = new Instances(name, v, 20000);

        double [][][] rdata = new double[ref.numRays()][][];
        double [][][] vdata = new double[dop.numRays()][][];
        double [][][] sdata = new double[dop.numRays()][][];

        for(int j = 0; j < ref.numRays(); j++)
        {
            double [] data = ref.getRay(j).getReflectivityData();

            rdata[j] = new double[data.length][4];
            for(int k = 0; k < data.length; k++)
                rdata[j][k][0] = data[k];
        }


        for(int j = 0; j < dop.numRays(); j++)
        {
            double [] data = dop.getRay(j).getVelocityData();
            vdata[j] = new double[data.length][4];
            for(int k = 0; k < data.length; k++)
                vdata[j][k][0] = data[k];

            data = dop.getRay(j).getSpectrumWidthData();
            sdata[j] = new double[data.length][4];
            for(int k = 0; k < data.length; k++)
                sdata[j][k][0] = data[k];
        }

        if(extended)
        {
            DataUtility.calcFeatures(rdata, convolutionWidth);
            DataUtility.calcFeatures(vdata, convolutionWidth);
            DataUtility.calcFeatures(sdata, convolutionWidth);
        }

        boolean mixedGateSize = (ref.getRay(0).getReflectivityGateSize() != dop.getRay(0).getVelocityGateSize());

        for(int j = 0; j < ref.numRays(); j++)
        {
            double azimuth = ref.getRay(j).getAzimuth();
            int [] neighbors = findNeighbors(dop, azimuth);

            for(int i = 0; i < rdata[j].length; i++)
            {
                if(rdata[j][i][0] == (double)Ray.EMPTY)
                    continue;

                if((mixedGateSize ? (i * 4 + 4) : i) < vdata[neighbors[0]].length)
                {
                    double [] features = new double[featureCount];
                    int featureIndex = 0;

                    if(!polar)
                    {
                        double r = i;
                        double theta = azimuth - 90;
                        if(theta < 0)
                            theta += 360;
                        if(theta != 0)
                            theta = 360 - theta;

                        features[featureIndex++] = r * Math.cos(theta * Math.PI / 180.0);
                        features[featureIndex++] = r * Math.sin(theta * Math.PI / 180.0);
                    }
                    else
                    {
                        features[featureIndex++] = azimuth;
                        features[featureIndex++] = i;
                    }

                    features[featureIndex++] = rdata[j][i][0];
                    features[featureIndex++] = averageNeighbors(vdata, 
                            neighbors[0], neighbors[1], j, 0, mixedGateSize);
                    features[featureIndex++] = averageNeighbors(sdata, 
                            neighbors[0], neighbors[1], j, 0, mixedGateSize);

                    if(extended)
                    {
                        features[featureIndex++] = rdata[j][i][1];
                        features[featureIndex++] = rdata[j][i][3];
                        features[featureIndex++] = rdata[j][i][2];
                        features[featureIndex++] = averageNeighbors(vdata, 
                                neighbors[0], neighbors[1], j, 1, mixedGateSize);
                        features[featureIndex++] = averageNeighbors(vdata, 
                                neighbors[0], neighbors[1], j, 3, mixedGateSize);
                        features[featureIndex++] = averageNeighbors(vdata, 
                                neighbors[0], neighbors[1], j, 2, mixedGateSize);
                        features[featureIndex++] = averageNeighbors(sdata, 
                                neighbors[0], neighbors[1], j, 1, mixedGateSize);
                        features[featureIndex++] = averageNeighbors(sdata, 
                                neighbors[0], neighbors[1], j, 3, mixedGateSize);
                        features[featureIndex++] = averageNeighbors(sdata, 
                                neighbors[0], neighbors[1], j, 2, mixedGateSize);
                    }

                    if(forClass)
                        features[featureIndex++] = 0;

                    result.add(new Instance(1.0, features));
                }
            }
        }

        if(forClass)
            result.setClassIndex(result.numAttributes() - 1);

        return result;
    }

    private double averageNeighbors(double[][][] data, int left, int right,
            int range, int axis, boolean mixedGateSize)
    {
        if(mixedGateSize)
        {
            int n = 0;
            double [] values = new double[8];
            for(int k = 0; k < 4; k++)
            {
                if(data[left][(range * 4) + k][axis] != (double)Ray.EMPTY)
                    values[n++] = data[left][(range * 4) + k][axis];
                if(data[right][(range * 4) + k][axis] != (double)Ray.EMPTY)
                    values[n++] = data[right][(range * 4) + k][axis];
            }
            return average(values, n);
        }
        else if(data[left][range][axis] == (double)Ray.EMPTY &&
                data[right][range][axis] != (double)Ray.EMPTY)
        {
            return data[right][range][axis];
        }
        else if(data[right][range][axis] == (double)Ray.EMPTY &&
                data[left][range][axis] != (double)Ray.EMPTY)
        {
            return data[left][range][axis];
        }
        else if(data[right][range][axis] != (double)Ray.EMPTY &&
                data[left][range][axis] != (double)Ray.EMPTY)
        {
            return (data[left][range][axis] + data[right][range][axis]) / 2.0;
        }
        else
        {
            return 0.0;
        }

    }

    public Instances convertToInstances(String name, Scan scan)
    {
        return convertToInstances(name, scan, false, false);
    }

    public Instances convertToInstances(String name, Scan scan, boolean polar, boolean forClassification)
    {
        int featureCount = forClassification ? 4 : 3;
        FastVector v = new FastVector(3);
        v.addElement(new Attribute("X"));
        v.addElement(new Attribute("Y"));
        v.addElement(new Attribute("VALUE"));
        if(forClassification)
        {
            FastVector classes = new FastVector(2);
            classes.addElement("0");
            classes.addElement("1");
            v.addElement(new Attribute("class", classes));
        }

        Instances result = new Instances(name, v, 20000);
        for(int j = 0; j < scan.numRays(); j++)
        {
            Ray ray = scan.getRay(j);
            double [] data = ray.getReflectivityData();
            for(int i = 0; i < data.length; i++)
            {
                if(data[i] == (double)Ray.EMPTY)
                    continue;

                double [] features = new double[featureCount];

                if(!polar)
                {
                    double r = i;
                    double theta = ray.getAzimuth() - 90;
                    if(theta < 0)
                        theta += 360;
                    if(theta != 0)
                        theta = 360 - theta;

                    features[0] = r * Math.cos(theta * Math.PI / 180.0);
                    features[1] = r * Math.sin(theta * Math.PI / 180.0);
                }
                else
                {
                    features[0] = ray.getAzimuth();
                    features[1] = i;
                }
                features[2] = data[i];
                if(forClassification)
                    features[3] = 0;

                result.add(new Instance(1.0, features));
            }
        }

        if(forClassification)
        {
            result.setClassIndex(3);
            File f = new File(name+".cl");
            if(f.exists())
            {
                try
                {
                    BufferedReader br = new BufferedReader(new FileReader(f));
                    String input = br.readLine();
                    int i = 0;
                    while(input != null)
                    {
                        result.instance(i++).setClassValue(Integer.parseInt(input.trim()));
                        input = br.readLine();
                    }
                    br.close();
                }
                catch(Exception e)
                {
                    System.out.println("Error Reading Cluster File");
                }
            }
        }
        return result;
    }

    private int [] findNeighbors(Scan scan, double az)
    {
        int[] result = new int[2];
        for(int i = 0; i < scan.numRays(); i++)
        {
            Ray current = scan.getRay(i);
            double newAz = current.getAzimuth();
            if(newAz > az)
            {
                result[1] = i;
                if(i == 0)
                    result[0] = scan.numRays() - 1;
                else
                    result[0] = i - 1;
                return result;
            }
        }
        result[0] = scan.numRays() - 1;
        result[1] = 0;
        return result;
    }

    private double average(double [] values, int length)
    {
        if(length == 0)
            return 0.0f;

        double sum = values[0];
        for(int i = 1; i < length; i++)
            sum += values[i];

        return sum / length;
    }

}
